/*
 * BufferManager.cpp
 *
 *  Created on: May 2, 2012
 *      Author: Ka Wai Lo
 */

//#define DEBUG

#include "BufferManager.hpp"

#include <fcntl.h>
#include <sys/stat.h>

using namespace std;

const int NULL_VECID = -1;

BufferManager::BufferManager(const std::string& filename, unsigned size) {
	#ifdef DEBUG
	cout << "BufferManager::BufferManager(" << filename << ", " << size << ") invoked.\n";
	#endif

	// ==== Open file for mmap() ====
	fd = open(filename.c_str(), O_RDWR);

	if (fd != -1) {
        struct stat st;
		stat(filename.c_str(), &st);
		unsigned long long lFileSize = st.st_size;

		iVecCnt = size;
		// ---- Determine iLastPageSize in case file size is not aligned to PAGE_SIZE ----
		iLastPageSize = 0xFFFFFFFF;
		iLastPageSize <<= PAGE_SIZE_OFFSET;
		iLastPageSize = ~iLastPageSize;
		iLastPageSize &= (int) lFileSize;
		iPageCnt = lFileSize >> PAGE_SIZE_OFFSET;
		// ---- Adjust iLastPageSize/iPageCnt based on alignment ----
		if (iLastPageSize == 0) { iLastPageSize = PAGE_SIZE; } // File size aligned to PAGE_SIZE
		else {
			#ifdef DEBUG
			cout << "- " << filename << " is not aligned to PAGE_SIZE: " << PAGE_SIZE << " bytes.\n";
			cout << "- Last page (pageId " << iPageCnt << ") has valid data of only " << iLastPageSize << " bytes.\n";
			#endif
			iPageCnt++;
		} // File size not aligned
		#ifdef DEBUG
		cout << "- iVecCnt: " << iVecCnt << " | iPageCnt: " << iPageCnt << " | iLastPageSize: " << iLastPageSize << "\n";
		#endif

		// ==== Initialise other variables ====
		vBuffer.resize(iVecCnt);
		vLock.resize(iVecCnt);
		viPageId.resize(iVecCnt);
		viVecId.resize(iPageCnt);
		vbAccessed.resize(iPageCnt);

		// ---- Initialise all iVecId ----
		for (int iVecId = 0; iVecId < iVecCnt; ++iVecId) {
			vBuffer.at(iVecId) = new BufferFrame(iVecId);
			vBuffer.at(iVecId)->init(fd);

			pthread_rwlock_init(&(vLock.at(iVecId)), NULL);

			liFree.push_back(iVecId); // Add this vecId to liFree
		}

		// ---- Initialise all iPageId ----
		for (unsigned iPageId = 0; iPageId < iPageCnt; ++iPageId) {
			viVecId.at(iPageId) = NULL_VECID;
			vbAccessed.at(iPageId) = false;
		}

		csFixNewPage = PTHREAD_MUTEX_INITIALIZER;
		csChangeList = PTHREAD_MUTEX_INITIALIZER;
		csFreePage = PTHREAD_MUTEX_INITIALIZER;
		csAccessLock = PTHREAD_MUTEX_INITIALIZER;
	}
	else {
		cerr << filename << " not exists.";
	}
}

BufferManager::~BufferManager(){
	for (int iVecId = 0; iVecId < iVecCnt; ++iVecId) {
		pthread_rwlock_destroy(&(vLock.at(iVecId)));
		delete vBuffer.at(iVecId);
	}
	close(fd);
}

BufferFrame& BufferManager::fixPage(unsigned pageId, bool exclusive) {
	#ifdef DEBUG
	cout << "BufferManager::fixPage(" << pageId << ", "<< exclusive << ") invoked.\n";
	cout << "liFree: ";
	for(list<int>::iterator i=liFree.begin(); i != liFree.end(); ++i) { cout << *i << " "; }
	cout << "\nliOnce: ";
	for(list<int>::iterator i=liOnce.begin(); i != liOnce.end(); ++i) { cout << *i << " "; }
	cout << "\nliHot: ";
	for(list<int>::iterator i=liHot.begin(); i != liHot.end(); ++i) { cout << *i << " "; }
	cout << "\nvbAccessed: ";
	for(unsigned i = 0; i < iPageCnt; ++i) { cout << vbAccessed.at(i) << " "; }
	cout << "\n";
	#endif

	// ==== Error checking ====
	if (pageId >= iPageCnt) {
		cerr << "BufferManager::fixPage(" << pageId << ", "<< exclusive << "): Out-of-bound. pageId >= " << iPageCnt << ".\n";
		throw;
	}

	// ==== Try getting vecId from pageId ====
	int iVecId = viVecId.at(pageId);

	// ==== pageId is not mapped, need to free a page ====
	if (iVecId == NULL_VECID) {
		pthread_mutex_lock(&csFixNewPage);
		iVecId = acquireVecId(pageId, exclusive);
		if (iVecId != NULL_VECID) {
			assignVecToPage(pageId, iVecId);
		}
		pthread_mutex_unlock(&csFixNewPage);
	}
	// ==== pageId is already mapped ====
	else {
		if (!(accessLockCS(iVecId, true, exclusive, true))) {
			cerr << "BufferManager::fixPage(" << pageId << ", "<< exclusive << "): Can't access lock.\n";
			throw;
		}
	}

	if (iVecId == NULL_VECID) {
		cerr << "BufferManager::fixPage(" << pageId << ", "<< exclusive << "): No more free frames while no frame can be freed.\n";
		throw;
	}

	#ifdef DEBUG
	cout << "liFree: ";
	for(list<int>::iterator i=liFree.begin(); i != liFree.end(); ++i) { cout << *i << " "; }
	cout << "\nliOnce: ";
	for(list<int>::iterator i=liOnce.begin(); i != liOnce.end(); ++i) { cout << *i << " "; }
	cout << "\nliHot: ";
	for(list<int>::iterator i=liHot.begin(); i != liHot.end(); ++i) { cout << *i << " "; }
	cout << "\nvbAccessed: ";
	for(unsigned i = 0; i < iPageCnt; ++i) { cout << vbAccessed.at(i) << " "; }
	cout << "\n";
	cout << "BufferManager::fixPage(" << pageId << ", "<< exclusive << ") return BufferFrame(vecId = " << iVecId << ").\n";
	#endif

	return *(vBuffer.at(iVecId));
}

// >>>> To find a free vecId. Return NULL_VECID is not found <<<<
int BufferManager::acquireVecId(const unsigned iPageId, const bool bExclusive) {
	#ifdef DEBUG
	cout << "BufferManager::acquireVecId(" << iPageId << ", "<< bExclusive << ") invoked.\n";
	#endif

	int iVecId = NULL_VECID;

//	pthread_mutex_lock(&csChangeList);
	// ==== liFree is not empty. Just pop one from it ====
	if (!(liFree.empty())) {
		iVecId = liFree.front();
		liFree.pop_front();
		accessLockCS(iVecId, true, bExclusive, true);
	}
	// ==== liFree is empty ====
	else {
		if (!(liOnce.empty())) {
			for(list<int>::iterator i=liOnce.begin(); i != liOnce.end(); ++i) {
				if (freeVec(*i, bExclusive)) {
					iVecId = *i;
					liOnce.erase(i);
					break;
				}
			}
		}

		if ((iVecId == NULL_VECID) && (!(liHot.empty()))) {
			for(list<int>::iterator i=liHot.begin(); i != liHot.end(); ++i) {
				if (freeVec(*i, bExclusive)) {
					iVecId = *i;
					liHot.erase(i);
					break;
				}
			}
		}

	}

	if (vbAccessed.at(iPageId)) {
		liHot.push_back(iVecId);
	}
	else {
		vbAccessed.at(iPageId) = true;
		liOnce.push_back(iVecId);
	}
//	pthread_mutex_unlock(&csChangeList);

	#ifdef DEBUG
	cout << "BufferManager::acquireVecId(" << iPageId << ", "<< bExclusive << ") return " << iVecId << ".\n";
	#endif

	return iVecId;
}

// >>>> Free vecId. Return true if success <<<<
inline bool BufferManager::freeVec(const int iVecId, const bool bExclusive) {
	#ifdef DEBUG
	cout << "BufferManager::freeVec(iVecId=" << iVecId << ", bExclusive=" << bExclusive << ") invoked.\n";
	#endif

	bool bSuccess = false;

	pthread_mutex_lock(&csFreePage);
	if (vBuffer.at(iVecId)->isAlloc()) {
		bSuccess = accessLockCS(iVecId, true, bExclusive, false);
		if (bSuccess) {
			viVecId.at(viPageId.at(iVecId)) = NULL_VECID;
			vBuffer.at(iVecId)->freePage();
		}
	}
	else { bSuccess = true; }
	pthread_mutex_unlock(&csFreePage);

	#ifdef DEBUG
	cout << "BufferManager::freeVec(iVecId=" << iVecId << ", bExclusive=" << bExclusive << ") return " << bSuccess << ".\n";
	#endif

	return bSuccess;
}

// >>>> Assign pageId to vecId. Return true if success <<<<
bool BufferManager::assignVecToPage(unsigned iPageId, int iVecId) {
	#ifdef DEBUG
	cout << "BufferManager::assignVecToPage(" << iPageId << ", "<< iVecId << ") invoked.\n";
	#endif

	bool bSuccess = false;

	viVecId.at(iPageId) = iVecId;
	viPageId.at(iVecId) = iPageId;

	if (iPageId == (iPageCnt - 1)) { vBuffer.at(iVecId)->mapPage(iPageId, iLastPageSize); }
	else { vBuffer.at(iVecId)->mapPage(iPageId, PAGE_SIZE); }
	bSuccess = true;

	#ifdef DEBUG
	cout << "BufferManager::assignVecToPage(" << iPageId << ", "<< iVecId << ") return " << bSuccess << ".\n";
	#endif

	return bSuccess;
}

// >>>> Unfix the page <<<<
void BufferManager::unfixPage(BufferFrame& frame, bool isDirty) {
	#ifdef DEBUG
	cout << "BufferManager::unfixPage(vecId=>" << frame.getVecId() << ", pageId=>" << frame.getPageId() << ", isDirty=" << isDirty << ") invoked.\n";
	#endif

	if (isDirty) { frame.setDirty(); }
	accessLockCS(frame.getVecId(), false, false ,false);

	#ifdef DEBUG
	cout << "BufferManager::unfixPage(vecId=>" << frame.getVecId() << ", pageId=>" << frame.getPageId() << ", isDirty=" << isDirty << ") return.\n";
	#endif
}

inline bool BufferManager::accessLockCS(const int iVecId, const bool bLock, const bool bExclusive, const bool bBlocking) {
	#ifdef DEBUG
	cout << "BufferManager::accessLockCS(vecId=>" << iVecId << ", bLock=>" << bLock << ", bExclusive=" << bExclusive << ") invoked.\n";
	#endif

	int iLockErr;

	if (bBlocking && bLock) {
		if (bExclusive) {iLockErr = pthread_rwlock_wrlock(&(vLock.at(iVecId))); }
		else { iLockErr = pthread_rwlock_rdlock(&(vLock.at(iVecId))); }
	}
	else {
		if (bLock) {
			//pthread_mutex_lock(&csAccessLock);
			if (bExclusive) { iLockErr = pthread_rwlock_trywrlock(&(vLock.at(iVecId))); }
			else { iLockErr = pthread_rwlock_tryrdlock(&(vLock.at(iVecId))); }
			//pthread_mutex_unlock(&csAccessLock);
		}
		else {
			iLockErr = pthread_rwlock_unlock(&(vLock.at(iVecId)));
		}
	}
//	if (bLock) {
//		if (bExclusive) {
//			if (bBlocking) { iLockErr = pthread_rwlock_wrlock(&csTemp); }
//			else { iLockErr = pthread_rwlock_trywrlock(&csTemp); }
//		}
//		else {
//			if (bBlocking) { iLockErr = pthread_rwlock_rdlock(&csTemp); }
//			else { iLockErr = pthread_rwlock_tryrdlock(&csTemp); }
//		}
//	}
//	else {
//		iLockErr = pthread_rwlock_unlock(&csTemp);
//	}
//	pthread_mutex_unlock(&csAccessLock);

	#ifdef DEBUG
	cout << "BufferManager::accessLockCS(vecId=>" << iVecId << ", bLock=>" << bLock << ", bExclusive=" << bExclusive << "): iLockErr=" << iLockErr << "\n";
	cout << "BufferManager::accessLockCS(vecId=>" << iVecId << ", bLock=>" << bLock << ", bExclusive=" << bExclusive << ") return " << (iLockErr == 0) << ".\n";
	#endif

	return (iLockErr == 0);
}
